using System;
using System.Drawing;
using System.Collections.Generic;
using Clandestine.Engines.Field;
using Clandestine.Graphics;
using Clandestine.Graphics.Text;

namespace Shlick
{
	public delegate void ToolHandler(int mouseX, int mouseY, int tileX, int tileY);
	public delegate void ToolCompleteHandler();
	
	public enum ToolTypes
	{
		TilePainter,
		TilePaster,
		ObjectMove,
		ObjectSelect,
		ObjectAdd,
		CollisionDebug
	}

	public class ToolManager
	{
		public MapDocument MapDocument { get; private set; }
		public Clandestine.Graphics.Graphics Graphics { get { return this.MapDocument.Graphics; } }
		public Map Map { get { return this.MapDocument.Map; } }
		public Tool CurrentTool { get; private set; }	
		public Dictionary<ToolTypes, Tool> Tools = new Dictionary<ToolTypes, Tool>();	
		private Point mousePosition;
		private Stack<Tool> toolStack = new Stack<Tool>();
		private ToolNameDisplay toolNameSprite;
	
		private Level activeLevel;
		public Level ActiveLevel 
		{
			get { return activeLevel; }
			set
			{
				Level old = activeLevel;
				activeLevel = value;
				CurrentTool.LevelChanged(old);
			}
		}
		
		private Layer activeLayer;
		public Layer ActiveLayer 
		{
			get { return activeLayer; } 
			set
			{
				Layer old = activeLayer;
				activeLayer = value;
				CurrentTool.LayerChanged(old);
			}
		}
		
		public ToolManager(MapDocument mapDocument)
		{
			this.MapDocument = mapDocument;
			
			toolNameSprite = new ToolNameDisplay(this.MapDocument, this.MapDocument.HudRenderList);
	
			// Make tools
			this.Tools.Add(ToolTypes.TilePainter, new TilePainterTool(this.MapDocument));
				this.Tools[ToolTypes.TilePainter].ToolComplete += OnToolComplete;
			this.Tools.Add(ToolTypes.ObjectMove, new ObjectMoveTool(this.MapDocument));
				this.Tools[ToolTypes.ObjectMove].ToolComplete += OnToolComplete;
			this.Tools.Add(ToolTypes.ObjectAdd, new ObjectAddTool(this.MapDocument));
				this.Tools[ToolTypes.ObjectAdd].ToolComplete += OnToolComplete;
			this.Tools.Add(ToolTypes.ObjectSelect, new ObjectSelectTool(this.MapDocument));
				this.Tools[ToolTypes.ObjectSelect].ToolComplete += OnToolComplete;
			this.Tools.Add(ToolTypes.CollisionDebug, new CollisionTool(this.MapDocument));
				this.Tools[ToolTypes.CollisionDebug].ToolComplete += OnToolComplete;
			this.Tools.Add(ToolTypes.TilePaster, new TilePasterTool(this.MapDocument));
				this.Tools[ToolTypes.TilePaster].ToolComplete += OnToolComplete;
			
			this.CurrentTool = this.Tools[ToolTypes.TilePainter];
			
			// Events!
			this.MapDocument.GLWidget.MouseMove += MousePositionCallback;
			this.MapDocument.GLWidget.MousePress += MouseButtonCallback;
			this.MapDocument.GLWidget.MouseRelease += MouseButtonCallback;
		}
		
		// Stops using the current tool and switches to the given one
		public void SwitchToTool(ToolTypes tool, params object[] args)
		{
			// Throw away stack
			lock (toolStack)
				toolStack.Clear();
			
			// Save old tool
			this.CurrentTool.Deactivated();
			
			// Annnd switch.
			this.CurrentTool = this.Tools[tool];
			this.CurrentTool.Activated(args);
		}
		
		// Doesn't stop using the current tool, but saves it's state and opens a new one
		public void OpenNewTool(ToolTypes tool, params object[] args) // name???
		{
			// Don't deactivate the old tool!
			lock (toolStack)
			{
				this.toolStack.Push(this.CurrentTool);
				this.CurrentTool = this.Tools[tool];
				this.CurrentTool.Activated(args);
			}
		}
		
		// Callback for when the current tool has finished what it's doing and wants to close
		public void OnToolComplete()
		{
			// Get one off the stack! Don't deactivate previous as it already cleans up before calling ToolComplete()
			lock (toolStack)
				this.CurrentTool = this.toolStack.Pop();
		}
		
		private Point cursorPosition
		{
			get
			{
				Qyoto.QPoint q1 = Qyoto.QCursor.Pos();
				Qyoto.QPoint curp = this.MapDocument.GLWidget.MapFromGlobal(q1);
				return new Point(curp.X(), curp.Y());
			}
		}
		
		public void MouseButtonCallback(int button, bool down)
        {
			// If we're not focussed, then we get a click, MouseButton will trigger before MousePosition
			//  - so we register a click wherever the mouse last was when focus was lost, which is *bad*.
			// So, always manually fire a mouse pos event!
			MousePositionCallback(cursorPosition);
			
            // Pressed down?
            if (down)
            {
                // Left?
                if (button == 0)
                    callHandler(CurrentTool.MouseDownLeft, mousePosition.X, mousePosition.Y);
                else // otherwise, right
                    callHandler(CurrentTool.MouseDownRight, mousePosition.X, mousePosition.Y);
            }
            else // not pressed down
            { 
                // Left?
                if (button == 0)
                    callHandler(CurrentTool.MouseUpLeft, mousePosition.X, mousePosition.Y);
                else // otherwise, right
                    callHandler(CurrentTool.MouseUpRight, mousePosition.X, mousePosition.Y);
            }
        }
		
		public void MousePositionCallback(Point p) { MousePositionCallback(p.X, p.Y); }

        public void MousePositionCallback(int x, int y)
        {
            // Remember the position as we don't get given it for button callbacks
            mousePosition = new Point(x, y);

            // do our callback
            callHandler(CurrentTool.MouseMove, x, y);
        }
        
        public void GetMousePosition(out int tileX, out int tileY)
        {
       		Camera Camera = this.Graphics.Camera;
        	tileX = ((int)(mousePosition.X / Camera.Zoom + Camera.Position.X)) / this.Map.GridSize;
            tileY = ((int)(mousePosition.Y / Camera.Zoom + Camera.Position.Y)) / this.Map.GridSize;
        }

        private void callHandler(ToolHandler func, int x, int y)
        {
            // Get mouse x and y w.r.t. the camera zoom and position
            Camera Camera = this.Graphics.Camera;
            int mouseX = (int)((x / Camera.Zoom) + Camera.Position.X);
            int mouseY = (int)((y / Camera.Zoom) + Camera.Position.Y);

            // Get tile positions
            int tileX, tileY;
            GetMousePosition(out tileX, out tileY);
            
            func(mouseX, mouseY, tileX, tileY);
        }
	}
}

